/**
 * Authors:
 * Vladimír Matěna
 * Jan Skalický
 */
package inireader;

import java.util.List;
import java.io.*;
import java.util.ArrayList;
import java.util.ListIterator;
import org.w3c.dom.NodeList;

/**
 * Class representing section in config file.
 */
public class Section
{
	private Identifier header;
	private String rawHeader;
	private boolean isMandatory;
	private boolean isValidated;
	private List<Property> properties;
	private Comment commentBefore;
	private Comment inlineComment;
	Configurator configurator;

	/**
	 * Creates section by name and configuration.
	 * @param name section name
	 * @param configurator configuration new section should belong to
	 */
	public Section(Identifier name, Configurator configurator)
	{
		this.configurator = configurator;
		setHeader(name);
		this.rawHeader = name.getId();
		this.properties = new ArrayList<Property>();
	}

	/**
	 * Creates new section from list of lines belonging to section
	 *	in configuration file.
	 * @param section list of lines
	 * @param configurator configuration section should belong to
	 */
	Section(List<String> section, Configurator configurator)
	{
		this.configurator = configurator;

		ListIterator<String> line = section.listIterator();

		// call order matters - first read comments then header
		readComments(line);
		readHeader(line);

		// read all newProperty
		this.properties = new ArrayList<Property>();
		while (line.hasNext())
		{
			List<String> property = new ArrayList<String>();

			// reads all lines belonging to newProperty
			while (line.hasNext())
			{
				String currentLine = line.next();
				property.add(currentLine);

				// reads line with comment before newProperty
				if (!currentLine.trim().startsWith(Configurator.COMMENT)
						&& currentLine.trim().length() != 0)
				{
					break;
				}
			}

			if (property.size() > 0)
			{
				properties.add(new Property(property, this));
			}
		}
	}

	/**
	 * Test section for being validates.
	 * @return true if validates, false otherwise
	 */
	public boolean isValidated()
	{
		return isValidated;
	}

	/**
	 * Tests section for being mandatory.
	 * @return true if mandatory, false otherwise
	 */
	public boolean isMandatory()
	{
		return isMandatory;
	}

	/**
	 * Gets section header.
	 * @return section header identifier
	 */
	public Identifier getHeader()
	{
		return header;
	}

	/**
	 * Reads comment before section.
	 * @param section the section
	 */
	private void readComments(ListIterator<String> section)
	{
		StringBuilder sectionComments = new StringBuilder();
		while (section.hasNext())
		{
			String currentLine = section.next();
			if (currentLine.contains(Configurator.SECTION_LEFT_BRACKET))
			{
				section.previous(); // jump back before header
				commentBefore = new Comment(sectionComments.toString());
				break;
			}
			else
			{
				sectionComments.append(currentLine);
				sectionComments.append(Configurator.NEWLINE);
			}
		}
	}

	/**
	 * Reads section header and possible comment after it.
	 * @param section
	 */
	private void readHeader(ListIterator<String> section)
	{
		String currentLine = section.next();

		// split comment after section header
		String comment = "";
		int commentIndex = currentLine.indexOf(Configurator.COMMENT);
		if (commentIndex > 0)
		{
			comment = currentLine.substring(commentIndex);
			currentLine = currentLine.substring(0, commentIndex);
		}
		inlineComment = new Comment(comment);

		// read header
		currentLine = currentLine.trim();
		if (currentLine.startsWith(Configurator.SECTION_LEFT_BRACKET)
				&& currentLine.endsWith(Configurator.SECTION_RIGHT_BRACKET))
		{
			rawHeader = currentLine.substring(1, currentLine.length() - 1);
			header = new Identifier(Configurator.trimBlank(rawHeader));
		}
		else
		{
			throw new IncorrectFormatException("Missing section header");
		}
	}

	/**
	 * Sets whenever section has been validated.
	 * @param isValidated true of validates, false otherwise
	 */
	void setValidated(boolean isValidated)
	{
		this.isValidated = isValidated;
	}

	/**
	 * Sets whenever section is mandatory.
	 * @param isMandatory true if mandatory, false otherwise
	 */
	public void setMandatory(boolean isMandatory)
	{
		this.isMandatory = isMandatory;
	}

	/**
	 * Sets section header.
	 * @param header new header
	 */
	public final void setHeader(Identifier header)
	{
		if(configurator.getSection(header.getId()) != null)
		{
			throw new ValidatingException(
							String.format("Cannot rename section to \"%s\"." +
							" Section with this name already exists!", header.getId()));
		}
		
		this.header = header;
		this.rawHeader = header.getId();

		configurator.validateAllElements();
	}

	/**
	 * Validates section by XML element describing schema and give validity.
	 * @param secElement XML element describing section schema
	 * @param validity validity level for test
	 */
	void validateSection(org.w3c.dom.Element secElement, Validity validity)
	{
		NodeList propNodes = secElement.getElementsByTagName("property");

		for (int i = 0; i < propNodes.getLength(); i++)
		{
			org.w3c.dom.Element propElement = (org.w3c.dom.Element)propNodes.item(i);

			Identifier name = new Identifier(propElement.getElementsByTagName("name").item(0).getTextContent());

			Property currProperty = getProperty(name.getId());

			if (currProperty == null)
			{
				boolean isPropMandatory = propElement.getAttribute("mandatory").matches("true");

				if (isPropMandatory)
				{
					throw new ValidatingException(String.format(
							"Mandatory property %s doesn't exist in ini file!",
							name.getId()));
				}

				Property newProp = new Property(
						name,
						isPropMandatory,
						propElement.getElementsByTagName("default_value").item(0).getTextContent(),
						this);
				properties.add(newProp);

				currProperty = newProp;
			}

			currProperty.validateProperty(propElement);
		}

		if (validity == Validity.Strict)
		{
			checkAllPropertiesValidated();
		}

		this.isValidated = true;
	}

	/**
	 * Checks whenever all properties was validated.
	 * If not complains about undefined properties.
	 */
	private void checkAllPropertiesValidated()
	{
		for (Property property : properties)
		{
			if (!property.isValidated())
			{
				throw new ValidatingException(String.format(
						"There is unspecified property %s in ini file!",
						property.getName().getId()));
			}
		}
	}

	/**
	 * Validates all elements of this section.
	 */
	void validateAllElements()
	{
		for (Property property : properties)
		{
			property.validateAllElements();
		}
	}

	/**
	 * Saves section to stream.
	 * @param output output stream
	 */
	void save(BufferedWriter output) throws IOException
	{
		if (commentBefore != null)
		{
			output.write(commentBefore.getContent());
		}
		output.write(Configurator.SECTION_LEFT_BRACKET);
		output.write(rawHeader);
		output.write(Configurator.SECTION_RIGHT_BRACKET);
		if(inlineComment != null)
		{
			output.write(inlineComment.getContent());
		}
		output.newLine();

		for (Property property : properties)
		{
			property.save(output);
		}
	}

	/**
	 * Formats section to human readable string. Used for debugging.
	 * @return String String representation of section
	 */
	@Override
	public String toString()
	{
		StringBuilder section = new StringBuilder();

		section.append("Sekce:\"");
		section.append(header.toString());
		section.append("\"");
		if (commentBefore != null)
		{
			section.append("\tCommentBefore:\"");
			section.append(commentBefore.getContent());
			section.append("\"");
			section.append("\tCommentAfter:\"");
			section.append(inlineComment.getContent());
			section.append("\"");
		}
		section.append(Configurator.NEWLINE);

		for (Property property : properties)
		{
			section.append(property.toString());
		}

		return section.toString();
	}

	/**
	 * Adds property to section.
	 * @param newProperty new Property
	 */
	public void addProperty(Property newProperty)
	{
		if (configurator.validity == Validity.Strict)
		{
			throw new ValidatingException(String.format(
					"Cannot add Property \"%s\"" +
					" to configuration using strict schema!",
					newProperty.getName().getId()));
		}

		if(newProperty.section != this)
		{
			throw new ValidatingException(String.format(
							"Cannot add property \"%s\". This property belongs to" +
							" another section.", newProperty.getName().getId()));
		}

		for(Property property:properties)
		{
			if(property.getName().equals(newProperty.getName()))
			{
				throw new ValidatingException(String.format(
								"Cannot add property \"%s\". Property with this name exists",
								property.getName().getId()));
			}
		}


		properties.add(newProperty);
		newProperty.validateAllElements();
	}

	/**
	 * Gets property with given name.
	 * @param id property name
	 */
	public Property getProperty(String id)
	{
		for (int i = 0; i < properties.size(); i++)
		{
			if (id.equals(properties.get(i).getName().getId()))
			{
				return properties.get(i);
			}
		}

		return null;
	}

	/**
	 * Remove property with given name.
	 * @param name property name
	 * @return true if property was removed
	 */
	public boolean removeProperty(String name)
	{
		boolean removed = false;

		for(Property property:properties)
		{
			if(property.getName().getId().equals(name))
			{
				if(!property.isMandatory())
				{
					throw new ValidatingException(String.format(
							"Cannot remove mandatory property \"%s\"!", name));
				}
				else
				{
					properties.remove(property);
				}
			}
		}

		configurator.validateAllElements();

		return removed;
	}

	/**
	 * Removes all properties from section.
	 */
	public void clearElements()
	{
		properties.clear();
		configurator.validateAllElements();
	}
}//end Section
